线程封装我们之前介绍过pthread的线程库,这个线程库主要是基于C语言的void*指针来进行传参和返回
我们使用C++的模板对其封装可以让他的使用更加方便,并且经过测试可以让我们更加直观的了解到线程互斥和同步的重要性
主要框架要对线程库进行封装,但是首先这个线程库的基本功能肯定要有
void*使用模板解决,函数指针使用包装器解决
那么基本框架就能搭建出来了
1234567891011121314151617181920212223242526272829303132333435363738#pragma once#include<iostream>#include<string>#include<functional>#include<pthread.h>template<class T>using Func_t = std::function<void(T)>; // 参数类型为T 返回值为空的函数template<class T>class Thread{public: Th...
设计模式我们以前学的面向过程、面向对象,还有封装、继承、多态
这些就像是武功里面的内功,练到了会用了就是会,而不会想学也都是从0到1,一步步学习练习
而设计模式就像是武功里的外功,属于是学会了,但是要怎么样才能把这个功力的作用最大化,在哪些条件下用什么样的内功能破解,研究的是这个问题
例如,我想设计一个只能在栈或堆上实例化的类应该怎么做,我想设计一个不能被拷贝或继承的类应该怎么做,这些就是外功,是有固定章法的
但是在C++中,设计模式关注的内容没有那么多那么全,我们暂时只了解单例模式即可
特殊类的设计不能被拷贝的类在C++11中,引入了delete关键字,可以直接禁用拷贝构造和赋值重载
在C++98中,虽然没有这个关键字,但是我们可以直接把类的拷贝构造和赋值重载设置为私有即可
12345678910111213141516171819// C++11class A {public: A(int x = 1) :num(x) {};、 A(const A& a) = delete; A& operator=(const A& a...
线程的概念线程是我们经常听到的一个概念,他和进程有什么关系呢
从操作系统课本里我们可能听说过,线程是一个微缩版的进程,他拥有TCB,不会被分配资源,是CPU进行调度的单位
我们其实可以更直观的理解,程序中的一个执行路线我们就可以称之为线程,也可以说是执行流
那么一个进程他的执行路线至少有一个,也就算做是一个线程,这个进程本身也可以创建分支线程,那么原来的这个进程也就叫做主线程
在Linux中,线程其实没有自己的TCB,而是和进程公用PCB,内存空间也是共享的
因此在Linux中线程其实是要比进程更加轻量化的
每个线程都拥有自己的PCB,但都是自己从主线程中写时拷贝来的
在CPU的视角来看,他的所调度的所有单位都是PCB,是同一个进程,CPU也无法分辨他运行的是线程还是进程
进程和线程对比线程其实是被CPU调度运行的的基本单位,因为一个CPU只能运行一个执行流
而进程是操作系统分配资源的基本单位,也就是说这个进程和他所属的线程内存空间全部共享,一个全局变量,不同线程之间也能共享
线程也拥有自己的特征数据,否则就无法对线程进行管理了
线程ID,栈区资源,信号屏蔽字,调度优先级
共享的...
信号的基本概念
信号递达:实际处理信号的动作
信号未决:信号从产生到递达之间的状态
信号阻塞:不会被递达的信号
信号忽略:递达的动作是忽略
阻塞和忽略的区别
阻塞指的是这个信号不会被递达,也就是不对其进行操作处理
忽略指的是这个信号可以递达,只是处理的动作是忽略
信号保存
在进程的PCB中有如下三个数据结构和信号相关
前两个是位图,后一个是数组
block位图这个位图表示哪些信号被阻塞,0或1表示是否被阻塞,某一个位置表示对应的信号
pending位图这个位图用来存储收到的信号,0或1表示是否收到,某一个位置表示对应的信号,这个位图也称之为信号集,也就是未决的情况
handler数组这个数组是一个函数指针数组,里面的内容是函数指针,下标表示收到n号信号,调用的处理方法就是对应的函数指针
SIG_DFL宏代表这个函数是默认处理函数
SIG_IGN宏代表收到这个信号后,进行忽略这个信号
信号处理
这个函数可以手动更改handler数组,让进程在捕捉到对应信号的时候调用我们指定的函数处理
1234567891011121314151617181920#include<iostr...
互斥锁现实中的锁有两种状态,打开和关闭,分别对应这资源可以被使用,和不可以被使用,我们可以通过使用钥匙对锁的状态进行改变
那么解决临界区的最简单的工具其实就是锁(mutex lock),我们称之为互斥锁
当一个进程进入临界区的时候,调用acquire函数,进行上锁,相当于不让别人再访问资源
退出临界区的时候调用release函数,来打开锁,可以让别人访问资源
一个锁只能上锁一次,因此在互斥锁中也有一个bool变量available,表示锁是否可以被上锁,可以被上锁时acquire函数就可以调用成功
这两个函数的执行必须是原子的,不允许被中断,因此通常都是硬件实现的
这种互斥锁也称之为自旋锁,主要问题是忙等待,也就是必须循环检查available是否可用
这种互斥锁可以用来解决同步问题
信号量我们之前简单了解过信号量,本质上其实就是描述资源的数量
可以用来解决互斥和同步的问题,他只能被wait和signal原语访问,也称为PV操作,分别对应着申请和释放资源两个操作
整形信号量这种就是最基础的用法,描述资源的数目
wait操作和P操作是申请操作,原理可以这样表示
12345void ...
信号及其产生与发送我们从生活中理解信号,例如各种指示灯,红绿灯之类的,我们能认识红绿灯是因为每一种不同的情况在我们大脑中已经存下了,并且根据每一种不同的情况我们也可以作出不同的反应
在操作系统中的信号也是如此,系统需要做的两个动作就是识别、处理
什么是Linux信号信号本身其实就是一种通知的形式,用户或者操作系统可以通过信号给进程发送信息,让进程来进行处理
例如前台进程可以发送一个ctrl + c指令来中断进程
我们可以使用kill -l查看进程信号表
这些个信号存在bits/signum.h文件中,大致可以分为两类,从1到31是标准信号,其余的都是POSIX实时信号,都有不同的含义
ctrl + c其实就对应着2号信号
回到上面的问题,进程想要处理信号就需要识别信号,那么进程如何识别信号,其实就是程序内置的代码可以识别+处理
信号的产生是随机的,这种随机性是在时间上随机,产生的信号随机
因此进程收到信号时可能正在处理其他事情,当收到信号也无法立即响应
那么就一定存在某种数据结构来保存收到的信号
因此信号的一生就只有三个过程,产生、保存、处理
信号的产生信号的产生有四种方式,分...
硬件实现方法之前的软件方法出现各种问题的根本原因其实是因为检查是否被占用和设置自己想要占用,这两个动作没有办法一气呵成导致的
而硬件实现的好处就是他的权限足够高,无法随意被中断
中断屏蔽方法操作系统有一个系统级的指令是中断指令,主要分为两个步骤,一个是关中断指令,另一个是开中断指令
什么意思呢,就是说是否允许被中断,关中断就之后不允许被中断,开中断就是之后可以被中断
因此我们想要确保进程访问临界区时不被其他进程打扰,只需要在临界区之前执行关中断指令,在临界区之后执行开中断指令,确保进程互斥的实现
123// 关中断;// 临界区;// 开中断;
这个方法的缺点是限制了CPU交替并发执行程序的能力,即便是时间片到也无法中断,因此系统的效率就会降低
其次这个方法其实是权限的下放,就是将系统的权限交给用户的进程,这样进程有可能会一直占用CPU
这个方法还无法限制其他CPU,只能限制一个CPU不执行相同临界区的代码
TestAndSet指令这是一条硬件指令,TS指令,这条指令是原子操作,也就是原语,不允许被中断
他的代码描述如下
123456bool TestAndSet(bool* ...
实现临界区互斥的基本方法我们说临界资源是在一段时间内只允许一个进程访问,而进程访问临界资源的那段代码称为临界区
为了防止两个进程同时进入临界区访问临界资源,我们设计的同步机制就需要满足下面的准则
空闲让进:不能占着茅坑不拉屎,当临界区空闲时可以允许另一个进程请求并进入临界区
忙则等待:不能别人上着呢冲进去也要拉,当临界区被占用时,其他请求访问的进程必须等待
有限等待:不能让等待的人憋死了,防止进程饥饿,要保证进程能在有限的时间内进入临界区
让权等待:不能占着临界区、阻塞了还得占着CPU,防止忙等待
软件实现一般这种方法都是基于全局变量的,让全局变量作为标志位
单标志法最简单的一个想法其实就是设一个bool变量,0时表示P0进程可以进入,1时表示P1进程可以进入,假设全局bool变量为flag
12345// P0进程while(flag != 0); // 当flag不为0时,说明不允许P0进入临界区,则循环等待 进入区// 访问临界资源... 临界区flag = 1; // 访问完了 要让给P1进程访问了 退出区// 其他代码 剩余区
12345// P1进程while(...